Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

opt: generate lookup joins on partitioned indexes #57690

Merged
merged 4 commits into from
Dec 10, 2020

Conversation

mgartner
Copy link
Collaborator

@mgartner mgartner commented Dec 8, 2020

xform: add rule expectations for GenerateLookupJoins tests

Release note: None

opt: generate lookup join for column constrained to multiple constants

Previously, the optimizer could create lookup join keys from filters
that constrain a column to a single constant value. This was done by
wrapping the join input in a Project that projected the constant value,
and using this new column as a key column.

This commit generalizes this behavior so that lookup join keys can also
be created from filters that constrain a column to multiple, non-ranging
constant values. The constant values are cross-joined with the input,
and the joined column is used as a key column. If a column is
constrained to a single constant value, the cross join normalizes to a
Project identical to the Projects constructed prior to this commit.

Release note (performance improvement): The query optimizer can use
filters that constrained columns to multiple constant values to generate
lookup joins. For example, a join filter x.a = y.a AND y.b IN (1, 2)
can be used to generate a lookup join on table y assuming that it has
an index on (a, b) or (b, a).

opt: generate lookup joins with CHECK constraints and computed columns

Previously, only explicit filters were used to generated lookup join key
columns. Now lookup join keys can be generated from CHECK constraints
and computed column expressions.

With this commit and the previous commit, lookup joins on partitioned
indexes are explored by the optimizer.

Release note (performance improvement): The query optimizer now explores
plans with lookup joins on partitioned indexes, resulting in more
efficient query plans in some cases.

xform: do not propagate join hints to GenerateLookupJoins cross joins

This commit fixes a bug that prevented a LOOKUP join hint from
producing a plan with a lookup join. Previously, the hint was propagated
to the synthesized cross join created as input to the lookup join. This
artificially inflated the cost of the cross join, making the lookup join
too costly to be selected as the optimal plan.

Release note: None

@mgartner mgartner requested a review from a team as a code owner December 8, 2020 00:54
@cockroach-teamcity
Copy link
Member

This change is Reviewable

@@ -1110,27 +1110,41 @@ limit
│ ├── fd: (10)-->(15)
│ ├── project
│ │ ├── columns: quantity:14!null accountname:10!null
│ │ ├── inner-join (hash)
│ │ ├── inner-join (lookup inventorydetails)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new query plan is better I think. Previously it was scanning inventorydetails (a large table) and only constraining by accountname (low cardinality). The new plan is a lookup join which is a "pseudo-scan" constraining accountname, dealerid, and cardid. What do you think @andy-kimball?

Previously, the optimizer could create lookup join keys from filters
that constrain a column to a single constant value. This was done by
wrapping the join input in a Project that projected the constant value,
and using this new column as a key column.

This commit generalizes this behavior so that lookup join keys can also
be created from filters that constrain a column to multiple, non-ranging
constant values. The constant values are cross-joined with the input,
and the joined column is used as a key column. If a column is
constrained to a single constant value, the cross join normalizes to a
Project identical to the Projects constructed prior to this commit.

Release note (performance improvement): The query optimizer can use
filters that constrained columns to multiple constant values to generate
lookup joins. For example, a join filter `x.a = y.a AND y.b IN (1, 2)`
can be used to generate a lookup join on table `y` assuming that it has
an index on `(a, b)` or `(b, a)`.
Copy link
Collaborator

@sumeerbhola sumeerbhola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @mgartner, @RaduBerinde, and @rytaft)


pkg/sql/opt/xform/testdata/rules/join, line 3011 at r5 (raw file):

 │    │    │    ├── (10,)
 │    │    │    ├── (20,)
 │    │    │    └── (30,)

This is interesting. What do we currently do with geo-partitioned regular indices when the input rows do not supply the partition column values and the values are injected using a predicate like the b IN (10, 20, 30) here. Does this mean we were unable to use the partitioned index currently?

Will this cross-join with a constant table also be how we inject these constants into the input row for feeding into the inverted join?

Do we know enough (in the optimizer) after doing this cross-join to ship the inputs prefixed by 10, 20, 30 to the relevant regions (when this is a partitioning field), so that the lookup can be local?

Copy link
Collaborator Author

@mgartner mgartner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @RaduBerinde and @rytaft)


pkg/sql/opt/xform/testdata/rules/join, line 3011 at r5 (raw file):

Does this mean we were unable to use the partitioned index currently?

Yes. Prior to this PR, we use partitioned indexes for other expressions like scans, but not for lookup joins.

Will this cross-join with a constant table also be how we inject these constants into the input row for feeding into the inverted join?

Yes, that's the plan. I had started work on inverted joins on partitioned inverted indexes, and realized that an inverted join is similar to a lookup join on a non-inverted index. Once I realized that lookup joins did not utilize partitioned indexes, I decided to start with that as a "warm-up" to the inverted join work.

Do we know enough (in the optimizer) after doing this cross-join to ship the inputs prefixed by 10, 20, 30 to the relevant regions (when this is a partitioning field), so that the lookup can be local?

I'm not 100% sure, but I think this information is available at the distsql level but not in the optimizer. @RaduBerinde or @rytaft may be able to answer this better.

Copy link
Member

@RaduBerinde RaduBerinde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm: Very cool!

Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @mgartner, @RaduBerinde, and @rytaft)


pkg/sql/opt/xform/testdata/rules/join, line 3295 at r5 (raw file):

----

opt expect=GenerateLookupJoinsWithFilter

[nit] maybe explain that the inlining rule converts the single-left row cross join with project

Copy link
Collaborator

@rytaft rytaft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewed 1 of 1 files at r1, 3 of 5 files at r2, 12 of 12 files at r4, 11 of 11 files at r5.
Reviewable status: :shipit: complete! 2 of 0 LGTMs obtained (waiting on @mgartner and @RaduBerinde)


pkg/sql/opt/xform/testdata/rules/join, line 3011 at r5 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

Does this mean we were unable to use the partitioned index currently?

Yes. Prior to this PR, we use partitioned indexes for other expressions like scans, but not for lookup joins.

Will this cross-join with a constant table also be how we inject these constants into the input row for feeding into the inverted join?

Yes, that's the plan. I had started work on inverted joins on partitioned inverted indexes, and realized that an inverted join is similar to a lookup join on a non-inverted index. Once I realized that lookup joins did not utilize partitioned indexes, I decided to start with that as a "warm-up" to the inverted join work.

Do we know enough (in the optimizer) after doing this cross-join to ship the inputs prefixed by 10, 20, 30 to the relevant regions (when this is a partitioning field), so that the lookup can be local?

I'm not 100% sure, but I think this information is available at the distsql level but not in the optimizer. @RaduBerinde or @rytaft may be able to answer this better.

@mgartner is right that this would happen at the DistSQL planner level, not at the optimizer level. But I'm pretty sure that what you're describing is not how lookup joins are planned, even by the DistSQL planner. Although @RaduBerinde or someone from SQL Execution would know better here.

Copy link
Collaborator

@sumeerbhola sumeerbhola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 2 of 0 LGTMs obtained (waiting on @mgartner and @RaduBerinde)


pkg/sql/opt/xform/testdata/rules/join, line 3011 at r5 (raw file):

Previously, rytaft (Rebecca Taft) wrote…

@mgartner is right that this would happen at the DistSQL planner level, not at the optimizer level. But I'm pretty sure that what you're describing is not how lookup joins are planned, even by the DistSQL planner. Although @RaduBerinde or someone from SQL Execution would know better here.

I think the lookup joins are currently distributed based on the locations the input happens to be generated at. If the input was already geo-partitioned, that seems fine. If we've generated the partitioning columns using the cross-join, like this example, there may be cases where "moving" the input to the relevant regions may be cheaper. For example, if the input is small, but the output of the join is large and is subsequently aggregated.

I am not suggesting any changes for this PR -- just curious about how query execution placement in a multi-region setting will behave.

@rytaft
Copy link
Collaborator

rytaft commented Dec 9, 2020


pkg/sql/opt/xform/testdata/rules/join, line 3011 at r5 (raw file):

Previously, sumeerbhola wrote…

I think the lookup joins are currently distributed based on the locations the input happens to be generated at. If the input was already geo-partitioned, that seems fine. If we've generated the partitioning columns using the cross-join, like this example, there may be cases where "moving" the input to the relevant regions may be cheaper. For example, if the input is small, but the output of the join is large and is subsequently aggregated.

I am not suggesting any changes for this PR -- just curious about how query execution placement in a multi-region setting will behave.

Yea, that's a good point. We'll have to see if this is a common use case, in which case it may be worth doing in the DistSQL planner. It seems non-trivial, though. Also, this might be something we can plan in the optimizer some day (we are eventually hoping to make some of the distribution decisions inside the optimizer).

Previously, only explicit filters were used to generated lookup join key
columns. Now lookup join keys can be generated from CHECK constraints
and computed column expressions.

With this commit and the previous commit, lookup joins on partitioned
indexes are explored by the optimizer.

Release note (performance improvement): The query optimizer now explores
plans with lookup joins on partitioned indexes, resulting in more
efficient query plans in some cases.
Copy link
Collaborator Author

@mgartner mgartner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 2 stale) (waiting on @RaduBerinde and @rytaft)


pkg/sql/opt/xform/testdata/rules/join, line 3295 at r5 (raw file):

Previously, RaduBerinde wrote…

[nit] maybe explain that the inlining rule converts the single-left row cross join with project

This happens in quite a few tests in this file, so instead of commenting on each one, I added some commentary to the GenerateLookupJoins function.

@rytaft
Copy link
Collaborator

rytaft commented Dec 9, 2020


pkg/sql/opt/xform/join_funcs.go, line 306 at r6 (raw file):

				},
			)
			lookupJoin.Input = c.e.f.ConstructInnerJoin(lookupJoin.Input, values, nil /* on */, joinPrivate)

Reminder from our discussion just now that this should probably not copy the join private

Copy link
Collaborator Author

@mgartner mgartner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 2 stale) (waiting on @RaduBerinde and @rytaft)


pkg/sql/opt/xform/join_funcs.go, line 306 at r6 (raw file):

Previously, rytaft (Rebecca Taft) wrote…

Reminder from our discussion just now that this should probably not copy the join private

Thanks! @rytaft @RaduBerinde PTAL at the new commit.

Copy link
Collaborator Author

@mgartner mgartner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 2 stale) (waiting on @RaduBerinde and @rytaft)


pkg/sql/opt/xform/join_funcs.go, line 306 at r6 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

Thanks! @rytaft @RaduBerinde PTAL at the new commit.

Also please confirm that my assumption that "reordering a cross-join is pointless". Or is there a benefit in a cross-join to having fewer rows in the left child than the right?

Copy link
Member

@RaduBerinde RaduBerinde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 2 stale) (waiting on @RaduBerinde and @rytaft)


pkg/sql/opt/xform/join_funcs.go, line 306 at r6 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

Also please confirm that my assumption that "reordering a cross-join is pointless". Or is there a benefit in a cross-join to having fewer rows in the left child than the right?

Well, we store the right side in memory completely (with fallback to disk if necessary). So if we're cross-joining 2 rows with 1M rows, we really want to store the small side.

Copy link
Collaborator Author

@mgartner mgartner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 2 stale) (waiting on @RaduBerinde and @rytaft)


pkg/sql/opt/xform/join_funcs.go, line 306 at r6 (raw file):

Previously, RaduBerinde wrote…

Well, we store the right side in memory completely (with fallback to disk if necessary). So if we're cross-joining 2 rows with 1M rows, we really want to store the small side.

If a lookup join is chosen as the optimal plan, its unlikely that either side of this child cross-join is very large. A lookup is performed for each output row of the cross-join, which would be costly if the cross-join produced many rows.

It seems sane to me to leave the constants on the right and not attempt reordering. The constants are a single column, so even in cases where there are more constants than input rows its plausible that the constants would have a smaller memory footprint.

But if you think its safer to permit reordering of this cross-join then I'm happy to. Also happy to try to benchmark the cost of this reordering vs no reordering if that helps us decide which direction to take.

Copy link
Member

@RaduBerinde RaduBerinde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 2 stale) (waiting on @RaduBerinde and @rytaft)


pkg/sql/opt/xform/join_funcs.go, line 306 at r6 (raw file):

Previously, mgartner (Marcus Gartner) wrote…

If a lookup join is chosen as the optimal plan, its unlikely that either side of this child cross-join is very large. A lookup is performed for each output row of the cross-join, which would be costly if the cross-join produced many rows.

It seems sane to me to leave the constants on the right and not attempt reordering. The constants are a single column, so even in cases where there are more constants than input rows its plausible that the constants would have a smaller memory footprint.

But if you think its safer to permit reordering of this cross-join then I'm happy to. Also happy to try to benchmark the cost of this reordering vs no reordering if that helps us decide which direction to take.

Having the constants on the right make sense, but why disable reordering? We should let other rules run as usual unless we have a strong reason to disable them.

Copy link
Collaborator

@rytaft rytaft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 1 files at r6, 3 of 3 files at r7.
Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 2 stale) (waiting on @RaduBerinde)


pkg/sql/opt/xform/join_funcs.go, line 306 at r6 (raw file):

Previously, RaduBerinde wrote…

Having the constants on the right make sense, but why disable reordering? We should let other rules run as usual unless we have a strong reason to disable them.

Yea, I'd also err on the side of just allowing reordering. I don't see a benefit in disabling it here.

Might also be worth adding a test that using an INNER LOOKUP JOIN hint works (since using the INNER INVERTED JOIN hint was how we identified the issue with copying the join private in the other case).

@mgartner mgartner force-pushed the partitioned-lookup-join branch 2 times, most recently from b507d14 to 8015b08 Compare December 10, 2020 00:09
Copy link
Collaborator Author

@mgartner mgartner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (and 2 stale) (waiting on @RaduBerinde and @rytaft)


pkg/sql/opt/xform/join_funcs.go, line 306 at r6 (raw file):

Previously, rytaft (Rebecca Taft) wrote…

Yea, I'd also err on the side of just allowing reordering. I don't see a benefit in disabling it here.

Might also be worth adding a test that using an INNER LOOKUP JOIN hint works (since using the INNER INVERTED JOIN hint was how we identified the issue with copying the join private in the other case).

SGTM. I've added a test with INNER LOOKUP JOIN and explained why the join flags aren't propagated in a comment. And updated the commit message and PR description.

This commit fixes a bug that prevented a `LOOKUP` join hint from
producing a plan with a lookup join. Previously, the hint was propagated
to the synthesized cross join created as input to the lookup join. This
artificially inflated the cost of the cross join, making the lookup join
too costly to be selected as the optimal plan.

Release note: None
Copy link
Collaborator

@rytaft rytaft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

Reviewed 4 of 4 files at r8.
Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (and 1 stale) (waiting on @RaduBerinde)

@mgartner
Copy link
Collaborator Author

TFTRs!

bors r+

@craig
Copy link
Contributor

craig bot commented Dec 10, 2020

Build succeeded:

@craig craig bot merged commit c41a2ed into cockroachdb:master Dec 10, 2020
@mgartner mgartner deleted the partitioned-lookup-join branch December 10, 2020 02:39
mgartner added a commit to mgartner/cockroach that referenced this pull request Feb 3, 2021
In cockroachdb#57690 a new code path was introduced from `findConstantFilterCols`
from `GenerateLookupJoins`. This new code path made it possible for the
filters passed to `findConstantFilterCols` to contain columns that are
not part of the given table. This violated the assumption that the
filter only contains columns in the given table and caused a panic. This
commit fixes the issue by neglecting constant filters for columns not in
the given table.

Fixes cockroachdb#59738

Release note (bug fix): A bug has been fixed that caused errors when
joining two tables when one of the tables had a computed column. This
bug was present since version 21.1.0-alpha.2 and not present in any
production releases.
mgartner added a commit to mgartner/cockroach that referenced this pull request Feb 3, 2021
In cockroachdb#57690 a new code path was introduced from `findConstantFilterCols`
from `GenerateLookupJoins`. This new code path made it possible for the
filters passed to `findConstantFilterCols` to contain columns that are
not part of the given table. This violated the assumption that the
filter only contains columns in the given table and caused a panic. This
commit fixes the issue by neglecting constant filters for columns not in
the given table.

Fixes cockroachdb#59738

Release note (bug fix): A bug has been fixed that caused errors when
joining two tables when one of the tables had a computed column. This
bug was present since version 21.1.0-alpha.2 and not present in any
production releases.
craig bot pushed a commit that referenced this pull request Feb 3, 2021
54201: roachtest: unskip cdc/crdb-chaos r=aayushshah15 a=aayushshah15

I ran this test a total of 15 times in parallel and wasn't able to
reproduce. Since its been skipped for 2+ releases, it's hard to know
what fixed it, but a good guess is #48561.

Release note: None

Fixes #37716 

Informs #36879

Release justification: testing only

57170: util/log: new experimental integration with Fluentd  r=itsbilal a=knz

Release note (cli change): It is now possible to redirect logging to
[Fluentd](https://www.fluentd.org)-compatible network collectors. See
the documentation for details. This is an alpha-quality feature.


59741: opt: fix panic in GenerateLookupJoin r=mgartner a=mgartner

#### opt: fix panic in GenerateLookupJoin

In #57690 a new code path was introduced from `findConstantFilterCols`
from `GenerateLookupJoins`. This new code path made it possible for the
filters passed to `findConstantFilterCols` to contain columns that are
not part of the given table. This violated the assumption that the
filter only contains columns in the given table and caused a panic. This
commit fixes the issue by neglecting constant filters for columns not in
the given table.

Fixes #59738

Release note (bug fix): A bug has been fixed that caused errors when
joining two tables when one of the tables had a computed column. This
bug was present since version 21.1.0-alpha.2 and not present in any
production releases.

#### opt: move findConstantFilterCols to general_funcs.go

Release note: None


59779: flowinfra: deflake a test r=yuzefovich a=yuzefovich

Previously, a unit test could fail in rare circumstances when relocating
a range to a remote node, and now we will use SucceedsSoon to avoid
that. Also unskip the vectorized option.

Fixes: #59712

Release note: None

Co-authored-by: Aayush Shah <aayush.shah15@gmail.com>
Co-authored-by: Raphael 'kena' Poss <knz@thaumogen.net>
Co-authored-by: Marcus Gartner <marcus@cockroachlabs.com>
Co-authored-by: Yahor Yuzefovich <yahor@cockroachlabs.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants